<?php

class fields
{
   private   $ERROR;
   protected $nolangs = FALSE;
   protected $belongs = array();
   public    $builded = array();

   protected $allfields = array();
   protected $nodes = array();
   protected $fieldsets = array();

   /**
    * Build fileds object
    *
    * @param integer $node
    */
   public function __construct()
   {
      $this->renewConnections();
      $this->init();
   }

   /**
    * Class destructor
    */
   public function __destruct()
   {
      if (config::read('cache_system') == 1 && !file_exists(ROOT . CACHE . '/meccano.fields.inc'))
      {
         file_put_contents(ROOT . CACHE . '/meccano.fields.inc', serialize($this));
      }
   }

   public function __sleep()
   {
      return array('allfields', 'belongs', 'builded', 'nodes', 'nolangs');
   }

   public function __wakeup()
   {
      $this->renewConnections();
   }

   /**
    * Get instance of Fields object
    *
    * @staticvar Fields $object_store_singelton
    * @param integer $node
    * @return Fields
    */
   static public function getInstance()
   {
      static $object_store_singelton;
      if (!is_object($object_store_singelton))
      {
         $object_store_singelton = fields::renewFromCache();
         if (!is_object($object_store_singelton))
         {
            $object_store_singelton = new fields();
         }
      }
      return $object_store_singelton;
   }

   /**
    * Renew Fields object from Cache
    *
    * @return object
    */
   public static function renewFromCache()
   {
      if(config::read('cache_system') == 1 && file_exists(ROOT . CACHE . '/meccano.fields.inc'))
      {
         if ($restored_object = unserialize(file_get_contents(ROOT . CACHE . '/meccano.fields.inc')))
         {
            return $restored_object;
         }
      }

      return FALSE;
   }

   /**
    * Unlink cache file for fields
    *
    * @param integer $node
    */
   public function clearFromCache()
   {
      if (file_exists(ROOT . CACHE . '/meccano.fields.inc'))
      {
         unlink(ROOT . CACHE . '/meccano.fields.inc');
      }
   }

   /**
    * Set attributes of class
    *
    * @param string $attrName
    * @param mixed $attrValue
    */
   public function attrSetter($attrName, $attrValue)
   {
      if (isset($this->$attrName))
      {
         switch ($attrName)
         {
            case 'nolangs':
               $this->nolangs = (bool) $attrValue;
         }
      }
   }

   /**
    *
    * @param <type> $node
    */
   public function getFields($node = 0, $params=array())
   {
      if (empty($this->builded[$node]))
      {
         $node = $this->buildFields($node);
      }

      # GET ALL FIELDS TREE BY NODE
      if (empty($params))
      {
         return $this->builded[$node];
      }

      $fields = $this->builded[$node];

      #FILTER BY BELONGING
      if(!empty($params['belong']) && !empty($fields->tabs[$params['belong']]))
      {
         $fields = $fields->tabs[$params['belong']];
      }

      return $fields;
   }


   /**
    * Get Fields object by ID
    *
    * @param integer $id
    * @return object
    */
   public function getField($id)
   {
      if(is_numeric($id) && isset($this->allfields[$id]))
      {
         return $this->allfields[$id];
      }
      return FALSE;
   }

   
   /**
    * Check if the field exists and belongs to the given type of content
    * 
    * @param string $alias
    * @param string $belong
    * @return boolean 
    */
   public function isFieldExist($alias, $belong = 'a')
   {
      foreach($this->allfields AS $f)
      {
         if($f->alias == $alias && $f->belong == $belong)
         {
            return $f;
         }
      }
      
      return FALSE;
   }


   /**
    * Get Info is field enabled or not
    *
    * @param string $alias
    * @param integer $node
    */
   public function isFieldEnabled($alias, $node = FALSE)
   {
      $fields = $this->splitFields(array('alias'=>$alias, 'active'=>TRUE, 'node'=>$node));

      if(empty($fields))
         return FALSE;
      else
         return reset($fields);
   }
   

   public function copyField($id, $dest_node)
   {
      $F = $this->getField($id);
      if(!empty($F) && $F->belong == 'u')
      {
         return $F;
      }

      if(!empty($F) && is_numeric($dest_node))
      {
         $DB = database::getInstance();
         $DB->sql = 'INSERT INTO `meccano_fields`
            (`node`, `active`, `tab`, `position`, `alias`, `type`, `lang_phrase`, `belong`, `required`, `translate`, `default`, `params`) 
            SELECT "'. $dest_node .'", `active`, `tab`, `position`, `alias`, `type`, `lang_phrase`, `belong`, `required`, `translate`, `default`, `params`
            FROM `meccano_fields`
            WHERE `id` = '. $F->id;
         if($DB->DB_Query())
         {
            $id = $DB->DB_LastID();
            $this->clearFromCache();
            $this->init();
            return $this->getField($id);
         }
      }

      return FALSE;
   }

   /**
    * Get flat array of fields
    * 
    * @param string $belong
    * @param integer $node
    * @param array $params
    * @param bool $only_active
    */
   public function splitFields($params = array())
   {
      $node = $this->getCacheNodes((!empty($params['node']) ? $params['node'] : 0));
      $node = end($node);
      if (empty($this->builded[$node]))
      {
         $this->buildFields($node);
      }

      return $this->builded[$node]->splitFields($params);
   }


   public function moveField($field, $relate, $relate_position = 'after')
   {
      if(is_numeric($field))
      {
         $field = $this->getField($field);
      }
      if(is_numeric($relate))
      {
         $relate = $this->getField($relate);
      }
      if($relate_position != 'before') $relate_position = 'after';
      if(is_object($field) && is_object($relate))
      {
         if($field->node == 0)
         {
            $new_position = $relate_position == 'before' ? $relate->position : $relate->position +1;
         } else
         {
            $new_position = $relate_position == 'before' ? $relate->position - 0.001 : $relate->position + 0.001;
         }
         
         $sql = array();
         $sql[] = 'UPDATE `meccano_fields` SET `position` = -'. $new_position .' WHERE `id` = '. $field->id;
         $sql[] = 'SET @p = '. ($field->node > 0 ? 0.001 : $new_position + 1);
         # SHIFT FIELDS
         $sql[] = 'UPDATE `meccano_fields`
               SET `position` = @p, @p:=@p+IF(`node` = 0, 1, 0.001)
               WHERE `position` >= "'. $new_position .'"
               AND `node` '. ($field->node > 0 ? ' > 0' : ' = 0') .'
               AND `tab` '. (empty($field->tab) ? ' IS NULL' : ' IS NOT NULL');
         $sql[] = 'UPDATE `meccano_fields` 
               SET `position` = "'. $new_position .'"'. (!empty($field->tab) && !empty($relate->tab) ? ', `tab` = "'. $relate->tab .'"' : '') .'
               WHERE `id` = '. $field->id;

         if($field->node == 0 && empty($field->tab))
         {
            # IF MOVING TABS IN DEFAULT FIELDSET (node == 0 && tab == NULL)
            
            //$sql = 'UPDATE `meccano_fields` SET `position` = '. ($relate_position == 'after' ? $relate->pos);
         }
      }

      return FALSE;
   }


   private function init()
   {
      $this->fieldsets = array();
      $this->nodes     = array();
      $this->allfields = array();
      $this->builded   = array();

      $DB = database::getInstance();

      $DB->sql = 'SELECT * FROM `meccano_fields` ORDER BY `node` DESC, `tab`, `belong` ASC';
      $DB->DB_Fetch();
      $rows = $DB->fetched;

      # COLLECT FIELDSETS TABS ALIASES
      for($i = 0, $t = $DB->rows; $i < $t; $i++)
      {
         if(!empty($rows[$i]['params']))
         {
            $params = $rows[$i]['params'];
            unset($rows[$i]['params']);
            $rows[$i] = array_merge($rows[$i], (array)  unserialize($params));
         }
         if(!empty($rows[$i]['fieldset'])) { array_push($this->fieldsets, $rows[$i]['alias']); }
      }

      for ($i = 0, $t = sizeof($rows); $i < $t; $i++)
      {
         $n = $rows[$i]['node'];
         $id = $rows[$i]['id'];

         $this->nodes[$n][$rows[$i]['belong'] . '_' . $rows[$i]['alias']] = $rows[$i];

         $class_name = 'fields_' . $rows[$i]['type'];

         # ALL FIELDS ARRAY
         $this->allfields[$id] = new $class_name($rows[$i]);
         if(in_array($rows[$i]['tab'], $this->fieldsets))
         {
            $this->allfields[$id]->fieldset = TRUE;
         }
      }
   }

   /**
    * Build fields Object
    *
    * @param integer $node
    * @return object
    */
   private function buildFields($node)
   {
      $nnn = $node;
      $nodes = $this->getCacheNodes($node);
      $node = end($nodes);

      if (!empty($this->builded[$node]))
      {
         return $node;
      }

      reset($nodes);
      $this->clearFromCache();
      # GETTING NEEDED IDs
      $fields4node = array();

      for ($i = 0, $t = sizeof($nodes); $i < $t; $i++)
      {
         if (!empty($this->nodes[$nodes[$i]]))
         {
            $fields4node = array_merge($fields4node, $this->nodes[$nodes[$i]]);
         }
      }

      # SEPARATE FIELDS AND TABS
      $fields = array();
      $tabs = array();
      foreach ($fields4node AS $value)
      {
         if (empty($value['tab']))
         {
            $tabs[] = $value;
         } else
         {
            $fields[] = $value;
         }
      }

      usort($fields, 'pSort');
      usort($tabs, 'pSort');

      $fieldList = new fieldsList();

      for ($i = 0, $t = sizeof($tabs); $i < $t; $i++)
      {
         $fieldList->addTab($this->allfields[$tabs[$i]['id']]);
      }

      for ($i = 0, $t = sizeof($fields); $i < $t; $i++)
      {
         $fieldList->addField($this->allfields[$fields[$i]['id']]);
      }

      $this->builded[$node] = $fieldList;

      return $node;
   }

   /**
    * Get nodes ID array for cache
    *
    * @param int $node
    * @return int
    */
   private function getCacheNodes($node)
   {
      if ($node == 0)
         return (array) $node;

      $pathList = tree_static::prebuildPathId($node);

      $nodes = array();
      if (!empty($pathList))
      {
         $pathList = array_reverse($pathList);

         for ($i = 0, $t = sizeof($pathList); $i < $t; $i++)
         {
            if (!empty($this->nodes[$pathList[$i]]))
            {
               $nodes[] = $pathList[$i];
            }
         }
      }
      array_unshift($nodes, 0);

      return $nodes;
   }

   /*
    * Renew class connection on creation or restoring from cache
    */

   private function renewConnections()
   {
      $this->ERROR = error::getInstance();
   }

}

/**
 * SuperClass for fields array (belonging to any type)
 */
class fieldsList
{

   public $tabs = array('a' => array(), 'c' => array(), 'u' => array());
   public $fields;

   public function save()
   {
      
   }

   public function addTab(&$tab)
   {
      if(empty($this->tabs[$tab->belong])) $this->tabs[$tab->belong] = new stdClass();
      $this->tabs[$tab->belong]->{$tab->alias} = clone $tab;
   }

   public function addField(&$field)
   {
      if (!is_object($field))
      {
         # TODO: generate error if passed not object
         return FALSE;
      }
      if (empty($this->tabs[$field->belong]) || empty($this->tabs[$field->belong]->{$field->tab}))
      {
         # TODO: generate error on absent tab
         return FALSE;
      }

      $this->fields[$field->belong . '_' . $field->alias] = $field;
      $this->tabs[$field->belong]->{$field->tab}->attachField($field);
   }

   /**
    * Split fields into array by params
    * 
    * @param <type> $params
    */
   public function splitFields($params)
   {
      $fields = array();
      foreach ($this->fields AS $f)
      {
         if (
                 (empty($params['belong']) || $params['belong'] == $f->belong) && # belong filter
                 (empty($params['active']) || $f->active == 1) &&                 # active filter
                 (empty($params['type']) || in_array($f->type, (array)$params['type']))       && # type filter
                 (empty($params['alias']) || in_array($f->alias, (array)$params['alias']))    && # alias filter
                 (empty($params['fieldset']) || ($f->fieldset == TRUE && $f->tab == $params['fieldset']))           # fieldset filter
         )
         {
            $fields[] = $f;
         }
      }

      return $fields;
   }

}